
// 主要完成三件事 1、响应式数据处理 2、模板编译  3、视图更新
class Vue {
    constructor(options) {
        this.$options = options;
        this.$data = options.data;

        // 1、数据响应式处理
        observe(this.$data);
        // 1.5、数据代理（通过Vue实例可以直接访问数据）
        proxy(this);
        // 2、编译模板
        new Compile(options.el, this);
    }
}
// 遍历模板树，解析其中动态部分，初始化并获得更新函数
class Compile{
    constructor(el, vm) {
        this.$vm = vm;
        // 获取宿主元素dom
        const dom = document.querySelector(el);
        // 编译
        this.compile(dom);
    }
    compile(el) {
        // 遍历el
        const childNodes = el.childNodes;
        childNodes.forEach(node => {
            if (this.isElement(node)) {
                // 元素节点 解析动态指令、属性绑定、事件
                const attrs = node.attributes;
                Array.from(attrs).forEach(attr => {
                   // 判断是否是一个动态的属性 k-text="counter"
                    const attrName = attr.name;
                    const exp = attr.value;
                    if (this.isDir(attrName)) {
                        const dir = attrName.substring(2);
                        console.log('11',dir)
                        // 判断是否是合法指令，如果是则执行处理函数
                        this[dir] && this[dir](node, exp);
                    }
                });
                
                if (node.childNodes.length > 0) {
                    this.compile(node);
                }
            } else if (this.isInter(node)) {
                // 插值绑定表达式
                this.compileText(node);
            }
        })
    }
    // 处理所有动态的绑定
    // dir指的是指令的名称
    update(node, exp, dir) {
        // 1、初始化dom内容
        const fn = this[dir + 'Updater'];
        fn && fn(node, this.$vm[exp]);
        // 2、创建watcher实例，负责后续更新操作
        new Watcher(this.$vm, exp, function (val) {
            fn && fn(node, val);            
        })
    }

    // k-text属性编译
    text(node, exp) {
        this.update(node, exp, 'text');
    }

    textUpdater(node, val) {
        node.textContent = val;
    }

    // k-html属性编译
    html(node, exp) {
        this.update(node, exp, 'html');
    }

    htmlUpdater(node, val) {
        node.innerHTML = val;
    }

    // 文本节点编译
    compileText(node) {
        this.text(node, RegExp.$1.trim())
        // node.textContent = this.$vm[RegExp.$1.trim()];
    }

    // 元素节点判断
    isElement(node) {
        return node.nodeType === 1;
    }

    // 插值表达式判断
    isInter(node) {
        return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
    }

    // 判断v-开头
    isDir(attrName) {
        return attrName.startsWith('k-');
    }
}

// 负责具体节点更新
class Watcher {
    constructor(vm, key, updater) {
        this.vm = vm;
        this.key = key;
        this.updater = updater;
        
        // 读当前值，触发依赖收集
        Dep.target = this;
        this.vm[this.key];
        Dep.target = null;
    }

    // Dep将来会调用update函数
    update() {
        const val = this.vm[this.key];
        this.updater.call(this.vm, val);
    }
}
// Dep和响应式的属性key之间又一一对应的关系
// 负责通知watchers更新
class Dep{
    constructor() {
        this.deps = []; // deps中存储的是watcher实例
    }
    addDep(dep) {
        // 此处的dep就是一个watch实例
        this.deps.push(dep);
    }
    notity() {
        this.deps.forEach(dep => {
            dep.update();
        })
    }
}

function proxy(vm) {
    Object.keys(vm.$data).forEach(key => {
        Object.defineProperty(vm, key, {
            get() {
                return vm.$data[key];
            },
            set(v) {
                vm.$data[key] = v;
            }
        })
    })
}

// 遍历传入对象的所有属性，执行响应式处理
function observe(obj) {
    if (typeof obj !== 'object' || obj == null) {
        return obj;
    }
    Object.keys(obj).forEach(key => defineReactive(obj, key, obj[key]));
}

function defineReactive(obj, key, val) {
    observe(val);

    const dep = new Dep();

    // 属性拦截
    Object.defineProperty(obj, key, {
        get() {
            Dep.target && dep.addDep(Dep.target);
            return val;
        },
        set(newValue) {
            console.log('set', key);
            val = newValue;
            dep.notity();
        }
    });
}

// 响应式对象追加响应式属性
// function set(obj, key, val) {
//     observe(val);
//     defineReactive(obj, key, val);
// }

export default Vue;